/*
 * GeomFx.fx
 *
 * Copyright (c) 2010 Remi Barraquand <dev at barraquand.com>. All rights reserved.
 *
 * This file is part of FxTools
 *
 * FxTools is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * FxTools is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with FxTools. If not, see <http://www.gnu.org/licenses/>.
 */
 
package com.barraquand.labs.jfxtools.geom;

import com.sun.javafx.geom.Path2D;
import com.sun.javafx.geom.Line2D;
import com.sun.javafx.geom.PathIterator;

import javafx.util.Math;
import java.lang.Exception;
import com.sun.javafx.geom.transform.Affine2D;
import com.sun.javafx.geom.transform.BaseTransform;
import javafx.geometry.Bounds;
import javafx.scene.shape.Polygon;

/**
 * Helper function that provides javafx with 2D basic geometric operation.
 *
 * @author Remi Barraquand <dev at barraquand.com>
 */

/**
* Build up a Path2D out of a sequence of coordinates.
*/
public function pointsToPath2D(sequence: Number[]): Path2D {
    if( sequence.size() mod 2 != 0 or sequence.size() < 2 ) { throw new Exception("Path must contains an even number of coordinates with a minimum of 2.")}
    var path: Path2D = Path2D {}
    path.moveTo(sequence[0] as Float, sequence[1] as Float);
    for (index in [2..sequence.size()-1 step 2]) {
        path.lineTo(sequence[index], sequence[index+1]);
    }
    return path;
}

/**
* Build up a Polygon from a Path2D.
*/
public function path2DToPolygon(path: Path2D): Polygon {
    var it: PathIterator = path.getPathIterator(null);
    var points: Number[];
    while( not it.isDone() ) {
        var seg = [1..2] as nativearray of Float;
        it.currentSegment(seg);
        insert seg[0] into points;
        insert seg[1] into points;
        it.next();
    }

    return Polygon {
        points: points
    };
}

/**
* Build up an Affine2D transform, required for Path2D transform.
*/
public function createAffine2D(rotate: Float, centerX: Float, centerY: Float, translateX: Float, translateY: Float, scaleX: Float, scaleY: Float): Affine2D {
    var transform: Affine2D = Affine2D {};

    transform.concatenate(BaseTransform.getTranslateInstance(translateX+centerX, translateY+centerY));
    transform.concatenate(BaseTransform.getRotateInstance(Math.toRadians(rotate), 0, 0));
    transform.concatenate(BaseTransform.getTranslateInstance(-centerX, -centerY));
    transform.concatenate(BaseTransform.getScaleInstance(scaleX, scaleY));

    return transform;
}

/**
* Get the minimum distance between to line segments.
*
* A line segment is the finite portion of a line that lies between
* two points.
*
* If the lines through [P1,P2] and [Q1,Q2] intersect, and both
* line segments include the point of intersection, then the distance
* is zero and we are done.
*
* Therefore, we compute the intersection of the two lines, and
* find the coordinates of that intersection point on each line.
* This will tell us if the zero distance case has occurred.
*
* Otherwise, let PN and QN be points in [P1,P2] and [Q1,Q2] for which
* the distance is minimal.  If the lines do not intersect, then it
* cannot be the case that both PN and QN are strictly interior to their
* line segments, aside from the exceptional singular case when
* the line segments overlap or are parallel.  Even then, one of PN
* and QN may be taken to be a segment endpoint.
*
* Therefore, our second computation finds the minimum of:
*
*   Distance ( P1, [Q1,Q2] );
*   Distance ( P2, [Q1,Q2] );
*   Distance ( Q1, [P1,P2] );
*   Distance ( Q2, [P1,P2] );
*
* @see http://people.sc.fsu.edu/~jburkardt/m_src/geometry/segments_dist_2d.m
*/
public function minDistance(line1: Line2D, line2: Line2D): Float {
    if( line1.intersectsLine(line2) ) {
        return 0.0;
    } else {
        var d1 = line1.ptSegDist(line2.x1,line2.y1);
        var d2 = line1.ptSegDist(line2.x2,line2.y2);
        var d3 = line2.ptSegDist(line1.x1,line1.y1);
        var d4 = line2.ptSegDist(line1.x2,line1.y2);

        return Math.min(d1, Math.min(d2, Math.min(d3, d4)));
    }
}

public function minDistance(bb1: Bounds, bb2: Bounds): Float {
    var path1: Line2D[] = [ new Line2D(bb1.minX, bb1.minY, bb1.maxX, bb1.minY),
                            new Line2D(bb1.maxX,  bb1.minY,  bb1.maxX,  bb1.maxY),
                            new Line2D(bb1.maxX,  bb1.maxY,  bb1.minX,  bb1.maxY),
                            new Line2D(bb1.minX,  bb1.maxY,  bb1.minX,  bb1.minY) ];

    var path2: Line2D[] = [ new Line2D(bb2.minX,  bb2.minY,  bb2.maxX,  bb2.minY),
                            new Line2D(bb2.maxX,  bb2.minY,  bb2.maxX,  bb2.maxY),
                            new Line2D(bb2.maxX,  bb2.maxY,  bb2.minX,  bb2.maxY),
                            new Line2D(bb2.minX,  bb2.maxY,  bb2.minX,  bb2.minY) ];

    var minDistance: Float = Float.MAX_VALUE;
    var tmpDistance: Float = Float.MAX_VALUE;
    
    var it1 = path1.iterator();
    var it2 = path2.iterator();

    while( it1.hasNext() ) {
        var line1 = it1.next();
        while( it2.hasNext() ) {
            var line2 = it2.next();
            tmpDistance = GeomFx.minDistance(line1, line2);
            if (tmpDistance < minDistance) {
                minDistance = tmpDistance;
            }
        }
    }

    return minDistance;
}


/**
* Get the minimum distance between two Path2D
*/
public function minDistance(path1: Path2D, path2: Path2D): Float {
    // in case the path is not bigger than a triangle
    if (path1.getNumCommands() < 3 or path2.getNumCommands() < 3 ) {
        return Float.MAX_VALUE;
    }

    var minDistance: Float = Float.MAX_VALUE;
    var tmpDistance: Float = Float.MAX_VALUE;

    var it1: PathIterator;
    var it2: PathIterator;
    var lineP1: Line2D = Line2D{};
    var lineP2: Line2D = Line2D{};
    var seg = [1..2] as nativearray of Float;

    it1 = path1.getPathIterator(null);
    it1.currentSegment(seg);
    lineP1.x1 = seg[0];
    lineP1.y1 = seg[1];
    while ( not it1.isDone()) {
        if( it1.currentSegment(seg) == PathIterator.SEG_MOVETO ) {
            it1.next();
            continue;
        }

        lineP1.x2 = seg[0];
        lineP1.y2 = seg[1];

        it2 = path2.getPathIterator(null);
        it2.currentSegment(seg);
        lineP2.x1 = seg[0];
        lineP2.y1 = seg[1];
        while ( not it2.isDone()) {
            if( it2.currentSegment(seg) == PathIterator.SEG_MOVETO ) {
                it2.next();
                continue;
            }
            lineP2.x2 = seg[0];
            lineP2.y2 = seg[1];
            tmpDistance = GeomFx.minDistance(lineP1, lineP2);
            if (tmpDistance < minDistance) {
                minDistance = tmpDistance;
            }
            lineP2.x1 = lineP2.x2;
            lineP2.y1 = lineP2.y2;
            it2.next();
        }
        lineP1.x1 = lineP1.x2;
        lineP1.y1 = lineP1.y2;
        it1.next();
    }

    return minDistance;
}

/**
* Return true if a Path2D contains another one.
*/
public function contains(path1: Path2D, path2: Path2D): Boolean {
    var contains: Boolean = Boolean.TRUE;

    var seg = [1..2] as nativearray of Float;
    var it = path2.getPathIterator(null);
    
    while ( not it.isDone() and contains ) {
        if( it.currentSegment(seg) != PathIterator.SEG_MOVETO ) {
            contains = contains and path1.contains(seg[0], seg[1]);
        }
        it.next();
    }

    return contains;
}
